﻿/*
 * Chroniker is a simple timer to record the amount of active time you spend working on a task.
 * Author:  Stephen Daivs
 * Company: dTec Studios LLC
 * Web:     www.dTec-Studios.com
 */


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Management;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Win32;

namespace Chroniker
{
    public partial class Form1 : Form
    {
        #region External References
        internal struct LASTINPUTINFO {
            public uint cbSize;
            public uint dwTime;
        }

        [DllImport("user32.dll")]
        static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
        #endregion

        #region Variables
        bool dragging = false;                                                      // user currently dragging
        bool active = false;                                                        // app is actively recording
        bool disconnect = false;                                                    // user disconnected from console
        bool paused = false;                                                        // user paused recording
        int total = 0;                                                              // total minutes recorded
        decimal[] qHr = { 1.0M, 2.0M, 3.0M, 4.0M };                                 // quarter hour values 
        decimal lastQtr;                                                            // last quarter hour logged
        string logFile = "Chroniker.log";                                           // chroniker log file name
        Menu menu;
        Color color = Color.DarkOrange;
        Color colorActive = Color.DarkOrange;
        Color colorIdle;
        DateTime startTime = DateTime.Now;
        Point dragAt;

        // menu option variables
        int optTimeOut = 5;                                                         // timeout minutes
        bool optLogging = true;                                                     // write to log
        string optLogPath = @"C:\";                                                 // path to log files
        #endregion

        #region Constructor
        public Form1() {
            InitializeComponent();

            // command-line arguments
            try {
                string[] args = Environment.GetCommandLineArgs();
                total = Convert.ToInt32(args[1]);
            }
            catch (Exception ex) { total = 0; }            
            
            // initialize options menu
            menu = new Menu();
            menu.ToggleRun += new EventHandler(ToggleRun);

            // initialize options
            this.colorActive = Properties.Settings.Default.Color;
            this.colorIdle = IdleColor(); ;
            this.optLogging = Properties.Settings.Default.Logging;
            this.optLogPath = Properties.Settings.Default.LogPath;
            this.optTimeOut = Properties.Settings.Default.Timeout;

            // register for windows session event
            SystemEvents.SessionSwitch += new SessionSwitchEventHandler(SystemEvents_SessionSwitch);
        }
        #endregion

        #region Drag/Drop
        /// <summary>
        /// Start drag operation
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        public void Grab(int x, int y) {
            this.Capture = true;
            dragging = true;
            dragAt = new Point(x, y);
        }
        /// <summary>
        /// End drag operation
        /// </summary>
        /// <param name="sender"></param>
        public void Drop(object sender) {
            this.Capture = false;
            dragging = false;
        }
        #endregion

        #region Form Event Handlers
        private void Form_Load(object sender, EventArgs e) {
            this.Location = Properties.Settings.Default.Location;
            this.colorActive = Properties.Settings.Default.Color;
            this.optLogging = Properties.Settings.Default.Logging;
            this.optLogPath = Properties.Settings.Default.LogPath;
            this.optTimeOut = Properties.Settings.Default.Timeout;
        }
        private void Form_Closing(object sender, FormClosingEventArgs e) {
            Properties.Settings.Default.Location = this.Location;
            Properties.Settings.Default.Color = this.colorActive;
            Properties.Settings.Default.Logging = this.optLogging;
            Properties.Settings.Default.LogPath = this.optLogPath;
            Properties.Settings.Default.Timeout = this.optTimeOut;
            Properties.Settings.Default.Save();
        }
        private void Mouse_Down(object sender, MouseEventArgs e) {
            if (e.X >= 1 && e.X <= 12) {
                // menu click
                DoMenu(); 
            }
            else {
                // moving
                int Y = e.Y;
                if (sender == pMenu) { Y += pMenu.Top; }
                Grab(e.X, Y);
            }
        }
        private void Mouse_Move(object sender, MouseEventArgs e) {
            // handle mouse over menu button
            pMenu.BackgroundImage = Properties.Resources.Menu;
            if (e.X >= 1 && e.X <= 12) {
                pMenu.BackgroundImage = Properties.Resources.MenuHover;
            }

            // handle dragging
            if (dragging) {
                this.Left = (e.X + this.Left) - dragAt.X;
                this.Top = (e.Y + this.Top) - dragAt.Y;
            }
            else {
                dragAt = new Point(e.X, e.Y);
            }
        }
        private void Mouse_Up(object sender, MouseEventArgs e) {
            if (e.Button == System.Windows.Forms.MouseButtons.Right) { this.Close(); return; }
            Drop(sender);
        }
        /// <summary>
        /// Mouse Leave for menu panel
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Menu_Leave(object sender, EventArgs e) {
            pMenu.BackgroundImage = Properties.Resources.Menu;
        }
        #endregion

        #region Timer Event Handlers
        /// <summary>
        /// Timer to keep track of elasped time
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Timer_Tick(object sender, EventArgs e) {
            // total up recorded time
            TimeSpan span = DateTime.Now.Subtract(startTime);
            TimeSpan totalSpan = new TimeSpan(0, this.total + (span.Hours*60) + span.Minutes, 0);

            // update display
            lblTimer.ForeColor = colorActive;
            lblTimer.Text = string.Format("{0:D2}:{1:D2}", totalSpan.Hours, totalSpan.Minutes);

            // log 
            if (optLogging) {
                decimal quarterHour = totalSpan.Minutes / 15.0M;
                if (qHr.Contains(quarterHour)) { WriteLog(quarterHour); }
            }
        }
        /// <summary>
        /// Master timer monitoring idle conditions
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Master_Tick(object sender, EventArgs e) {
            bool idle = GetLastInputTime() > (optTimeOut * 60);
            int mins = 0;

            if (idle || disconnect || paused)  {                                    // user is idle, disconnected from console or paused
                if (this.active) {
                    // calculate amount of active time
                    TimeSpan span = DateTime.Now.Subtract(startTime);               // timespan since last start time
                    mins = (span.Hours*60) + span.Minutes;                          // active minutes
                    if (idle) mins -= optTimeOut;                                   // subtract off idle time
                    mins = mins < 0 ? 0 : mins;                                     // positive value check
                    this.total += mins;                                             // add to total minutes
                    TimeSpan sum = new TimeSpan(0, this.total, 0);                  // new timespan for total time

                    // update display
                    color = colorIdle;
                    lblTimer.ForeColor = color;
                    lblTimer.Text = string.Format("{0:D2}:{1:D2}", sum.Hours, sum.Minutes);

                    timer1.Enabled = false;
                    active = false;                                                 // set app active state
                }
            }
            else {
                if (!this.active) { 
                    // reactiveate
                    startTime = DateTime.Now;                                       // set a new start time 
                    this.active = true;                                             // set app active state
                    color = colorActive;
                }

                timer1.Enabled = true;
            }
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Show the options menu
        /// </summary>
        private void DoMenu() {
            DialogResult result = menu.ShowDialog();

            if (result == System.Windows.Forms.DialogResult.OK) {
                this.colorActive = Properties.Settings.Default.Color;
                this.colorIdle = IdleColor(); ;
                this.optLogging = Properties.Settings.Default.Logging;
                this.optLogPath = Properties.Settings.Default.LogPath;
                this.optTimeOut = Properties.Settings.Default.Timeout;
            }
        }
        /// <summary>
        /// Create a darker color from active color
        /// </summary>
        /// <returns></returns>
        private Color IdleColor() {
            int offset = 50;
            int newRed = (this.colorActive.R - offset) < 0 ? 0 : (this.colorActive.R - offset);
            int newGreen = (this.colorActive.G - offset) < 0 ? 0 : (this.colorActive.G - offset);
            int newBlue = (this.colorActive.B - offset) < 0 ? 0 : (this.colorActive.B - offset);
            return (Color.FromArgb(newRed, newGreen, newBlue));
        }
        /// <summary>
        /// Handle event from options menu
        /// </summary>
        private void ToggleRun(object sender, EventArgs e) {
            this.paused = !this.paused;
        }
        /// <summary>
        /// Write to log
        /// </summary>
        private void WriteLog(decimal qtrHr) {
            if (optLogPath == "") { return; }

            if (qtrHr != lastQtr) {
                try {
                    using (StreamWriter file = new StreamWriter(Path.Combine(optLogPath, logFile), true)) {
                        file.WriteLine(string.Format("{0:MM/dd/yyyy HH:mm} {1}", DateTime.Now, lblTimer.Text));
                    }
                    lastQtr = qtrHr;
                }
                catch (Exception ex) { }
            }
        }
        #endregion

        #region Windows Utilities
        /// <summary>
        /// Get amount of idle time for user
        /// </summary>
        /// <returns></returns>
        static uint GetLastInputTime() {
            uint idleTime = 0;
            LASTINPUTINFO lastInputInfo = new LASTINPUTINFO();
            lastInputInfo.cbSize = (uint)Marshal.SizeOf(lastInputInfo);
            lastInputInfo.dwTime = 0;

            uint envTicks = (uint)Environment.TickCount;

            if (GetLastInputInfo(ref lastInputInfo)) {
                uint lastInputTick = lastInputInfo.dwTime;
                idleTime = envTicks - lastInputTick;
            }

            return ((idleTime > 0) ? (idleTime / 1000) : 0);
        }
        /// <summary>
        /// Detect switched users or locked computer win session changes
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e) {
            switch (e.Reason) {
                case SessionSwitchReason.ConsoleConnect:
                    this.disconnect = false;
                    break;
                case SessionSwitchReason.ConsoleDisconnect:
                    this.disconnect = true;
                    break;
            }
        }
        #endregion


    }
}
